package min3d.core;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import min3d.Min3d;
import min3d.Shared;
import min3d.animation.AnimationObject3d;
import min3d.vos.FrustumManaged;
import min3d.vos.Light;
import min3d.vos.RenderType;
import min3d.vos.TextureVo;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.util.Log;

public class Renderer implements GLSurfaceView.Renderer {
	private static final String			TAG							= "Renderer";

	public static final int				NUM_GLLIGHTS				= 8;

	private static final int			FPS_MAX						= 30;
	private static final int			FPS_MAX_DELAY				= 1000 / FPS_MAX;

	private OnTouchInterface			mOnTouchInterface;
	private OnOffsetsChangedInterface	mOnOffsetsChangedInterface;
	private OnSurfaceChangedInterface	mOnSurfaceChangedInterface;
	private boolean						isPreview;

	private GL10						_gl;
	private Scene						_scene;
	// private TextureManager _textureManager;

	private float						_surfaceAspectRatio;

	private IntBuffer					_scratchIntBuffer;
	private FloatBuffer					_scratchFloatBuffer;
	private boolean						_scratchB;

	// stats-related
	public static final int				FRAMERATE_SAMPLEINTERVAL_MS	= 1000;
	private boolean						_logFps						= false;
	private long						_frameCount					= 0;
	private long						_frameDelta					= 0;
	private float						_fps						= 0;
	private long						_timeLastSample;
	private ActivityManager				_activityManager;
	private ActivityManager.MemoryInfo	_memoryInfo;

	private boolean						mIsPreview;

	public Renderer(Scene $scene) {
		Log.w(TAG, "Init Renderer " + ismIsPreview());
		mOnTouchInterface = null;
		mOnOffsetsChangedInterface = null;

		_scene = $scene;

		_scratchIntBuffer = IntBuffer.allocate(4);
		_scratchFloatBuffer = FloatBuffer.allocate(4);

		_activityManager = (ActivityManager) Shared.context().getSystemService(Context.ACTIVITY_SERVICE);
		_memoryInfo = new ActivityManager.MemoryInfo();
		if (Shared.textureManager() == null)
		{
			TextureManager _textureManager = new TextureManager();
			Shared.textureManager(_textureManager);
		}

	}

	public void onSurfaceCreated(GL10 $gl, EGLConfig eglConfig) {
		Log.e(Min3d.TAG, "Renderer.onSurfaceCreated()");

		RenderCaps.setRenderCaps($gl);

		setGl($gl);

		reset();
		//
		_scene.init();
	}

	public void onSurfaceChanged(GL10 gl, int w, int h) {
		Log.e(Min3d.TAG, "Renderer.onSurfaceChanged()");
		onSurfaceChanged(w, h);

		setGl(_gl);

		// reset();

		// _scene.init();

		_surfaceAspectRatio = (float) w / (float) h;

		_gl.glViewport(0, 0, w, h);
		_gl.glMatrixMode(GL10.GL_PROJECTION);
		_gl.glLoadIdentity();

		updateViewFrustrum();

		_timeLastSample = System.currentTimeMillis();
	}

	public void onDrawFrame(GL10 gl) {
		// Upload texture
		long frame_draw_start = System.currentTimeMillis();

		if (_scene.ismUpdateTexture())
		{

			Shared.textureManager().addTextureId(_scene.getmUpdateBitmap(), _scene.getmUpdateName(), false);
			// _scene.getmUpdateBitmap().recycle();
			//
			// // Log.w(TAG, "Need texture update");
			_scene.setmUpdateTexture(false);
		}

		// Update 'model'
		_scene.update();

		// Update 'view'
		drawSetup();
		drawScene();

		if (_logFps)
			doFps();

		// Get delta
		long now = System.currentTimeMillis();
		setDelta(now - _timeLastSample);
		_timeLastSample = now;

		long mDelay = now - frame_draw_start;
		if (mDelay < FPS_MAX_DELAY)
		{
			try
			{
				Thread.sleep(FPS_MAX_DELAY - mDelay);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}

	}

	//

	/**
	 * Accessor to the GL object, in case anything outside this class wants to do bad things with it :)
	 */
	public GL10 gl() {
		return _gl;
	}

	/**
	 * Returns last sampled framerate (logFps must be set to true)
	 */
	public float fps() {
		return _fps;
	}

	/**
	 * Return available system memory in bytes
	 */
	public long availMem() {
		_activityManager.getMemoryInfo(_memoryInfo);
		return _memoryInfo.availMem;
	}

	protected void drawSetup() {
		// View frustrum

		if (_scene.camera().frustum.isDirty())
		{
			updateViewFrustrum();
		}

		// Camera

		_gl.glMatrixMode(GL10.GL_MODELVIEW);
		_gl.glLoadIdentity();

		GLU.gluLookAt(_gl, _scene.camera().position.x, _scene.camera().position.y, _scene.camera().position.z, _scene.camera().target.x, _scene.camera().target.y, _scene.camera().target.z, _scene.camera().upAxis.x, _scene.camera().upAxis.y,
				_scene.camera().upAxis.z);

		// Background color

		if (_scene.backgroundColor().isDirty())
		{
			_gl.glClearColor((float) _scene.backgroundColor().r() / 255f, (float) _scene.backgroundColor().g() / 255f, (float) _scene.backgroundColor().b() / 255f, (float) _scene.backgroundColor().a() / 255f);
			_scene.backgroundColor().clearDirtyFlag();
		}

		_gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

		drawSetupLights();

		// Always on:
		_gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
	}

	protected void drawSetupLights() {
		// GL_LIGHTS enabled/disabled based on enabledDirty list
		for (int glIndex = 0; glIndex < NUM_GLLIGHTS; glIndex++)
		{
			if (_scene.lights().glIndexEnabledDirty()[glIndex] == true)
			{
				if (_scene.lights().glIndexEnabled()[glIndex] == true)
				{
					_gl.glEnable(GL10.GL_LIGHT0 + glIndex);

					// make light's properties dirty to force update
					_scene.lights().getLightByGlIndex(glIndex).setAllDirty();
				}
				else
				{
					_gl.glDisable(GL10.GL_LIGHT0 + glIndex);
				}

				_scene.lights().glIndexEnabledDirty()[glIndex] = false; // clear dirtyflag
			}
		}

		// Lights' properties

		Light[] lights = _scene.lights().toArray();
		for (int i = 0; i < lights.length; i++)
		{
			Light light = lights[i];

			if (light.isDirty()) // .. something has changed
			{
				// Check all of Light's properties for dirty

				int glLightId = GL10.GL_LIGHT0 + _scene.lights().getGlIndexByLight(light);

				if (light.position.isDirty())
				{
					light.commitPositionAndTypeBuffer();
					_gl.glLightfv(glLightId, GL10.GL_POSITION, light._positionAndTypeBuffer);
					light.position.clearDirtyFlag();
				}
				if (light.ambient.isDirty())
				{
					light.ambient.commitToFloatBuffer();
					_gl.glLightfv(glLightId, GL10.GL_AMBIENT, light.ambient.floatBuffer());
					light.ambient.clearDirtyFlag();
				}
				if (light.diffuse.isDirty())
				{
					light.diffuse.commitToFloatBuffer();
					_gl.glLightfv(glLightId, GL10.GL_DIFFUSE, light.diffuse.floatBuffer());
					light.diffuse.clearDirtyFlag();
				}
				if (light.specular.isDirty())
				{
					light.specular.commitToFloatBuffer();
					_gl.glLightfv(glLightId, GL10.GL_SPECULAR, light.specular.floatBuffer());
					light.specular.clearDirtyFlag();
				}
				if (light.emissive.isDirty())
				{
					light.emissive.commitToFloatBuffer();
					_gl.glLightfv(glLightId, GL10.GL_EMISSION, light.emissive.floatBuffer());
					light.emissive.clearDirtyFlag();
				}

				if (light.direction.isDirty())
				{
					light.direction.commitToFloatBuffer();
					_gl.glLightfv(glLightId, GL10.GL_SPOT_DIRECTION, light.direction.floatBuffer());
					light.direction.clearDirtyFlag();
				}
				if (light._spotCutoffAngle.isDirty())
				{
					_gl.glLightf(glLightId, GL10.GL_SPOT_CUTOFF, light._spotCutoffAngle.get());
				}
				if (light._spotExponent.isDirty())
				{
					_gl.glLightf(glLightId, GL10.GL_SPOT_EXPONENT, light._spotExponent.get());
				}

				if (light._isVisible.isDirty())
				{
					if (light.isVisible())
					{
						_gl.glEnable(glLightId);
					}
					else
					{
						_gl.glDisable(glLightId);
					}
					light._isVisible.clearDirtyFlag();
				}

				if (light._attenuation.isDirty())
				{
					_gl.glLightf(glLightId, GL10.GL_CONSTANT_ATTENUATION, light._attenuation.getX());
					_gl.glLightf(glLightId, GL10.GL_LINEAR_ATTENUATION, light._attenuation.getY());
					_gl.glLightf(glLightId, GL10.GL_QUADRATIC_ATTENUATION, light._attenuation.getZ());
				}

				light.clearDirtyFlag();
			}
		}
	}

	protected void drawScene() {
		if (_scene.fogEnabled() == true)
		{
			_gl.glFogf(GL10.GL_FOG_MODE, _scene.fogType().glValue());
			_gl.glFogf(GL10.GL_FOG_START, _scene.fogNear());
			_gl.glFogf(GL10.GL_FOG_END, _scene.fogFar());
			_gl.glFogfv(GL10.GL_FOG_COLOR, _scene.fogColor().toFloatBuffer());
			_gl.glEnable(GL10.GL_FOG);
		}
		else
		{
			_gl.glDisable(GL10.GL_FOG);
		}

		for (int i = 0; i < _scene.children().size(); i++)
		{
			Object3d o = _scene.children().get(i);
			if (o.animationEnabled())
			{
				((AnimationObject3d) o).update();
			}
			drawObject(o);
		}

	}

	// boolean customResult = o.customRenderer(_gl);
	// if (customResult) return;

	protected void drawObject(Object3d $o) {
		if ($o.isVisible() == false)
			return;

		// Various per-object settings:

		// Normals

		if ($o.hasNormals() && $o.normalsEnabled())
		{
			$o.vertices().normals().buffer().position(0);
			_gl.glNormalPointer(GL10.GL_FLOAT, 0, $o.vertices().normals().buffer());
			_gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
		}
		else
		{
			_gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
		}

		// Is lighting enabled for object...

		/*
		 * // *** this version not working properly on emulator - why not? *** _scratchIntBuffer.position(0); _gl.glGetIntegerv(GL10.GL_LIGHTING, _scratchIntBuffer); if (useLighting != _scratchIntBuffer.get(0)) { if (useLighting == 1) {
		 * _gl.glEnable(GL10.GL_LIGHTING); } else { _gl.glDisable(GL10.GL_LIGHTING); } }
		 */

		boolean useLighting = (_scene.lightingEnabled() && $o.hasNormals() && $o.normalsEnabled() && $o.lightingEnabled());
		if (useLighting)
		{
			_gl.glEnable(GL10.GL_LIGHTING);
		}
		else
		{
			_gl.glDisable(GL10.GL_LIGHTING);
		}

		// Shademodel

		_gl.glGetIntegerv(GL11.GL_SHADE_MODEL, _scratchIntBuffer);
		if ($o.shadeModel().glConstant() != _scratchIntBuffer.get(0))
		{
			_gl.glShadeModel($o.shadeModel().glConstant());
		}

		// Colors: either per-vertex, or per-object

		if ($o.hasVertexColors() && $o.vertexColorsEnabled())
		{
			$o.vertices().colors().buffer().position(0);
			_gl.glColorPointer(4, GL10.GL_UNSIGNED_BYTE, 0, $o.vertices().colors().buffer());
			_gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		}
		else
		{
			_gl.glColor4f((float) $o.defaultColor().r / 255f, (float) $o.defaultColor().g / 255f, (float) $o.defaultColor().b / 255f, (float) $o.defaultColor().a / 255f);
			_gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
		}

		// Colormaterial

		_gl.glGetIntegerv(GL10.GL_COLOR_MATERIAL, _scratchIntBuffer);
		_scratchB = (_scratchIntBuffer.get(0) != 0);
		if ($o.colorMaterialEnabled() != _scratchB)
		{
			if ($o.colorMaterialEnabled())
				_gl.glEnable(GL10.GL_COLOR_MATERIAL);
			else
				_gl.glDisable(GL10.GL_COLOR_MATERIAL);
		}

		// Point size

		if ($o.renderType() == RenderType.POINTS)
		{
			if ($o.pointSmoothing())
				_gl.glEnable(GL10.GL_POINT_SMOOTH);
			else
				_gl.glDisable(GL10.GL_POINT_SMOOTH);

			_gl.glPointSize($o.pointSize());
		}

		// Line properties

		if ($o.renderType() == RenderType.LINES || $o.renderType() == RenderType.LINE_STRIP || $o.renderType() == RenderType.LINE_LOOP)
		{
			if ($o.lineSmoothing() == true)
			{
				_gl.glEnable(GL10.GL_LINE_SMOOTH);
			}
			else
			{
				_gl.glDisable(GL10.GL_LINE_SMOOTH);
			}

			_gl.glLineWidth($o.lineWidth());
		}

		// Backface culling

		if ($o.doubleSidedEnabled())
		{
			_gl.glDisable(GL10.GL_CULL_FACE);
		}
		else
		{
			_gl.glEnable(GL10.GL_CULL_FACE);
		}

		drawObject_textures($o);

		// Matrix operations in modelview

		_gl.glPushMatrix();

		_gl.glTranslatef($o.position().x, $o.position().y, $o.position().z);

		_gl.glRotatef($o.rotation().x, 1, 0, 0);
		_gl.glRotatef($o.rotation().y, 0, 1, 0);
		_gl.glRotatef($o.rotation().z, 0, 0, 1);

		_gl.glScalef($o.scale().x, $o.scale().y, $o.scale().z);

		// Draw

		$o.vertices().points().buffer().position(0);
		_gl.glVertexPointer(3, GL10.GL_FLOAT, 0, $o.vertices().points().buffer());

		if (!$o.ignoreFaces())
		{
			int pos, len;

			if (!$o.faces().renderSubsetEnabled())
			{
				pos = 0;
				len = $o.faces().size();
			}
			else
			{
				pos = $o.faces().renderSubsetStartIndex() * FacesBufferedList.PROPERTIES_PER_ELEMENT;
				len = $o.faces().renderSubsetLength();
			}

			$o.faces().buffer().position(pos);

			_gl.glDrawElements($o.renderType().glValue(), len * FacesBufferedList.PROPERTIES_PER_ELEMENT, GL10.GL_UNSIGNED_SHORT, $o.faces().buffer());
		}
		else
		{
			_gl.glDrawArrays($o.renderType().glValue(), 0, $o.vertices().size());
		}

		//
		// Recurse on children
		//

		if ($o instanceof Object3dContainer)
		{
			Object3dContainer container = (Object3dContainer) $o;

			for (int i = 0; i < container.children().size(); i++)
			{
				Object3d o = container.children().get(i);
				drawObject(o);
			}
		}

		// Restore matrix

		_gl.glPopMatrix();
	}

	private void drawObject_textures(Object3d $o) {
		// iterate thru object's textures

		for (int i = 0; i < RenderCaps.maxTextureUnits(); i++)
		{
			_gl.glActiveTexture(GL10.GL_TEXTURE0 + i);
			_gl.glClientActiveTexture(GL10.GL_TEXTURE0 + i);

			if ($o.hasUvs() && $o.texturesEnabled())
			{
				$o.vertices().uvs().buffer().position(0);
				_gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, $o.vertices().uvs().buffer());

				TextureVo textureVo = ((i < $o.textures().size())) ? textureVo = $o.textures().get(i) : null;

				if (textureVo != null && Shared.textureManager().getGlTextureId(textureVo.textureId) != -0xDE)
				{
					// activate texture
					int glId = Shared.textureManager().getGlTextureId(textureVo.textureId);
					_gl.glBindTexture(GL10.GL_TEXTURE_2D, glId);
					_gl.glEnable(GL10.GL_TEXTURE_2D);
					_gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

					int minFilterType = Shared.textureManager().hasMipMap(textureVo.textureId) ? GL10.GL_LINEAR_MIPMAP_NEAREST : GL10.GL_NEAREST;
					_gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, minFilterType);
					_gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); // (OpenGL default)

					// do texture environment settings
					for (int j = 0; j < textureVo.textureEnvs.size(); j++)
					{
						_gl.glTexEnvx(GL10.GL_TEXTURE_ENV, textureVo.textureEnvs.get(j).pname, textureVo.textureEnvs.get(j).param);
					}

					// texture wrapping settings
					_gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, (textureVo.repeatU ? GL10.GL_REPEAT : GL10.GL_CLAMP_TO_EDGE));
					_gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, (textureVo.repeatV ? GL10.GL_REPEAT : GL10.GL_CLAMP_TO_EDGE));

					// texture offset, if any
					if (textureVo.offsetU != 0 || textureVo.offsetV != 0)
					{
						_gl.glMatrixMode(GL10.GL_TEXTURE);
						_gl.glLoadIdentity();
						_gl.glTranslatef(textureVo.offsetU, textureVo.offsetV, 0);
						_gl.glMatrixMode(GL10.GL_MODELVIEW); // .. restore matrixmode
					}
				}
				else
				{
					_gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
					_gl.glDisable(GL10.GL_TEXTURE_2D);
					_gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
				}
			}
			else
			{
				_gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
				_gl.glDisable(GL10.GL_TEXTURE_2D);
				_gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
			}

		}
	}

	/**
	 * Used by TextureManager
	 */
	int uploadTextureAndReturnId(Bitmap $bitmap, boolean $generateMipMap) /* package-private */
	{
		int glTextureId = 0;

		int[] a = new int[1];

		if (_gl != null)
		{
			// Log.w(TAG, "GL TEXTURE INIT " + _gl);
			_gl.glGenTextures(1, a, 0); // create a 'texture name' and put it in array element 0
			glTextureId = a[0];
			_gl.glBindTexture(GL10.GL_TEXTURE_2D, glTextureId);

			if ($generateMipMap && _gl instanceof GL11)
			{
				_gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE);
			}
			else
			{
				_gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_FALSE);
			}

			GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, $bitmap, 0);
		}
		return glTextureId;
	}

	/**
	 * Used by TextureManager
	 */
	void deleteTexture(int $glTextureId) /* package-private */
	{
		int[] a = new int[1];
		a[0] = $glTextureId;
		if (_gl != null)
		{
			_gl.glDeleteTextures(1, a, 0);
		}
	}

	protected void updateViewFrustrum() {
		FrustumManaged vf = _scene.camera().frustum;
		float n = vf.shortSideLength() / 2f;

		float lt, rt, btm, top;

		lt = vf.horizontalCenter() - n * _surfaceAspectRatio;
		rt = vf.horizontalCenter() + n * _surfaceAspectRatio;
		btm = vf.verticalCenter() - n * 1;
		top = vf.verticalCenter() + n * 1;

		if (_surfaceAspectRatio > 1)
		{
			lt *= 1f / _surfaceAspectRatio;
			rt *= 1f / _surfaceAspectRatio;
			btm *= 1f / _surfaceAspectRatio;
			top *= 1f / _surfaceAspectRatio;
		}

		_gl.glMatrixMode(GL10.GL_PROJECTION);
		_gl.glLoadIdentity();
		_gl.glFrustumf(lt, rt, btm, top, vf.zNear(), vf.zFar());

		vf.clearDirtyFlag();
	}

	/**
	 * If true, framerate and memory is periodically calculated and Log'ed, and gettable thru fps()
	 */
	public void logFps(boolean $b) {
		_logFps = $b;

		if (_logFps)
		{ // init
			_timeLastSample = System.currentTimeMillis();
			_frameCount = 0;
		}
	}

	private void setGl(GL10 $gl) {
		_gl = $gl;
	}

	private void doFps() {
		_frameCount++;

		long now = System.currentTimeMillis();
		long delta = now - _timeLastSample;

		if (delta >= FRAMERATE_SAMPLEINTERVAL_MS)
		{
			_fps = _frameCount / (delta / 1000f);

			_activityManager.getMemoryInfo(_memoryInfo);
			Log.v(Min3d.TAG, "FPS: " + Math.round(_fps) + ", availMem: " + Math.round(_memoryInfo.availMem / 1048576) + "MB");

			_timeLastSample = now;
			_frameCount = 0;
		}
	}

	private void reset() {
		// Log.w(TAG, "Texture reset");
		// Reset TextureManager
		// Shared.textureManager().reset();

		// Do OpenGL settings which we are using as defaults, or which we will not be changing on-draw

		// Explicit depth settings
		_gl.glEnable(GL10.GL_DEPTH_TEST);
		_gl.glClearDepthf(1.0f);
		_gl.glDepthFunc(GL10.GL_LESS);
		_gl.glDepthRangef(0, 1f);
		_gl.glDepthMask(true);

		// Alpha enabled
		_gl.glEnable(GL10.GL_BLEND);
		_gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);

		// "Transparency is best implemented using glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
		// with primitives sorted from farthest to nearest."

		// Texture
		_gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); // (OpenGL default is GL_NEAREST_MIPMAP)
		_gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); // (is OpenGL default)

		// CCW frontfaces only, by default
		_gl.glFrontFace(GL10.GL_CCW);
		_gl.glCullFace(GL10.GL_BACK);
		_gl.glEnable(GL10.GL_CULL_FACE);

		// Disable lights by default
		for (int i = GL10.GL_LIGHT0; i < GL10.GL_LIGHT0 + NUM_GLLIGHTS; i++)
		{
			_gl.glDisable(i);
		}

		//
		// Scene object init only happens here, when we get GL for the first time
		//
	}

	public void set_scene(Scene _scene) {
		this._scene = _scene;
	}

	// Slide interface

	public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels) {
		if (mOnOffsetsChangedInterface != null)
		{
			mOnOffsetsChangedInterface.onOffsetsChangedInterface(xOffset, yOffset, xStep, yStep, xPixels, yPixels);
		}
	}

	public void setOffsetsChangedInterfaceListener(OnOffsetsChangedInterface listener) {
		mOnOffsetsChangedInterface = listener;
	}

	public static interface OnOffsetsChangedInterface {
		public abstract void onOffsetsChangedInterface(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels);
	}

	// Touch interface

	public void onTouchUp(int x, int y) {
		// Log.w("Render", "On TOUCH UP");
		if (mOnTouchInterface != null)
		{
			mOnTouchInterface.onTouchInterface(x, y);
		}
	}

	public void setOnTouchInterfaceListener(OnTouchInterface listener) {
		mOnTouchInterface = listener;
	}

	public static interface OnTouchInterface {
		public abstract void onTouchInterface(int x, int y);
	}

	// Screen change interface

	public void onSurfaceChanged(int w, int h) {
		if (mOnSurfaceChangedInterface != null)
		{
			mOnSurfaceChangedInterface.onSurfaceChangedInterface(w, h);
		}
	}

	public void setOnSurfaceChangedInterfaceListener(OnSurfaceChangedInterface listener) {
		mOnSurfaceChangedInterface = listener;
	}

	public static interface OnSurfaceChangedInterface {
		public abstract void onSurfaceChangedInterface(int w, int h);
	}

	// Preview

	public boolean ismIsPreview() {
		return mIsPreview;
	}

	public void setmIsPreview(boolean mIsPreview) {
		this.mIsPreview = mIsPreview;
	}

	// Get screen aspect

	public float get_surfaceAspectRatio() {
		return _surfaceAspectRatio;
	}

	// Get delta

	public synchronized long getDelta() {
		return _frameDelta;
	}

	public synchronized void setDelta(long _frameDelta) {
		this._frameDelta = _frameDelta;
	}

}
